package com.xxxtea.cf;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 抽象推荐服务（协同过滤算法基类）
 * 
 * 提供通用的基于物品的协同过滤推荐算法实现，支持用户-物品评分矩阵、物品相似度计算、推荐结果生成等。
 * 子类需实现用户行为数据和物品详情的获取逻辑。
 * 
 * @param <T> 推荐项目类型（需实现RecommendItem接口）
 * @param <B> 用户行为类型（需实现UserBehavior接口）
 * @author badman
 * @since 1.0.0
 */
public abstract class AbstractRecommenderService<T extends RecommendItem, B extends UserBehavior> {

	/**
	 * 获取指定用户的行为数据
	 * @param userId 用户ID
	 * @return 用户行为列表
	 */
	protected abstract List<B> getUserBehaviors(Integer userId);

	/**
	 * 根据物品ID获取物品详情
	 * @param itemId 物品ID
	 * @return 物品对象
	 */
	protected abstract T getItemById(Integer itemId);

	/**
	 * 获取所有用户的行为数据
	 * @return 所有用户行为列表
	 */
	protected abstract List<B> getAllUserBehaviors();

	/**
	 * 构建用户-物品评分矩阵
	 * @param behaviors 用户行为列表
	 * @return 用户-物品评分矩阵（userId -> itemId -> score）
	 */
	protected Map<Integer, Map<Integer, Double>> buildUserItemMatrix(List<B> behaviors) {
		Map<Integer, Map<Integer, Double>> matrix = new HashMap<>();
		for (B behavior : behaviors) {
			matrix.computeIfAbsent(behavior.getUserId(), k -> new HashMap<>())
					.put(behavior.getItemId(), behavior.getScore());
		}
		return matrix;
	}

	/**
	 * 计算物品相似度矩阵（基于余弦相似度）
	 * @param userItemMatrix 用户-物品评分矩阵
	 * @return 物品相似度矩阵（itemId1 -> itemId2 -> similarity）
	 */
	protected Map<Integer, Map<Integer, Double>> calculateItemSimilarityMatrix(Map<Integer, Map<Integer, Double>> userItemMatrix) {
		Map<Integer, Map<Integer, Double>> similarityMatrix = new HashMap<>();

		// 获取所有物品ID
		Set<Integer> itemIds = userItemMatrix.values().stream()
				.flatMap(m -> m.keySet().stream())
				.collect(Collectors.toSet());

		// 计算物品间相似度
		for (Integer item1 : itemIds) {
			for (Integer item2 : itemIds) {
				if (item1.equals(item2)) continue;

				// 构建两个物品的用户评分向量
				Map<Integer, Double> vector1 = new HashMap<>();
				Map<Integer, Double> vector2 = new HashMap<>();

				// 对每个用户的评分进行遍历
				for (Map.Entry<Integer, Map<Integer, Double>> userRatings : userItemMatrix.entrySet()) {
					Integer userId = userRatings.getKey();
					Map<Integer, Double> ratings = userRatings.getValue();

					// 如果用户对这两个物品都有评分，则加入向量
					if (ratings.containsKey(item1)) {
						vector1.put(userId, ratings.get(item1));
					}
					if (ratings.containsKey(item2)) {
						vector2.put(userId, ratings.get(item2));
					}
				}

				// 只有当两个物品都有用户评分时才计算相似度
				if (!vector1.isEmpty() && !vector2.isEmpty()) {
					double similarity = this.calculateSimilarity(vector1, vector2);
					similarityMatrix.computeIfAbsent(item1, k -> new HashMap<>())
							.put(item2, similarity);
				}
			}
		}
		return similarityMatrix;
	}

	/**
	 * 生成推荐结果
	 * @param userId 用户ID
	 * @param limit 推荐数量
	 * @return 推荐项目列表
	 */
	public List<T> recommend(Integer userId, int limit) {
		// 获取用户行为数据
		List<B> userBehaviors = getUserBehaviors(userId);
		if (userBehaviors.isEmpty()) {
			return Collections.emptyList();
		}

		// 构建评分矩阵 userId, itemId -> score
		Map<Integer, Map<Integer, Double>> userItemMatrix = buildUserItemMatrix(getAllUserBehaviors());

		// 计算物品相似度矩阵 userId, itemId -> score
		Map<Integer, Map<Integer, Double>> itemSimilarityMatrix = calculateItemSimilarityMatrix(userItemMatrix);

		// 获取用户已交互的物品
		Set<Integer> interactedItems = userBehaviors.stream()
				.map(B::getItemId)
				.collect(Collectors.toSet());

		// 计算推荐分数
		Map<Integer, Double> recommendScores = new HashMap<>();
		for (B behavior : userBehaviors) {
			Integer userItem = behavior.getItemId();
			Map<Integer, Double> similarities = itemSimilarityMatrix.get(userItem);
			if (similarities == null) {
				continue;
			}
			for (Map.Entry<Integer, Double> entry : similarities.entrySet()) {
				Integer candidateItem = entry.getKey();
				if (!interactedItems.contains(candidateItem)) {
					recommendScores.merge(candidateItem, behavior.getScore() * entry.getValue(), Double::sum);
				}
			}
		}

		// 排序并返回推荐结果
		return recommendScores.entrySet().stream()
				.sorted(Map.Entry.<Integer, Double>comparingByValue().reversed())
				.limit(limit)
				.map(entry -> {
					T item = getItemById(entry.getKey());
					item.setScore(entry.getValue());
					return item;
				})
				.collect(Collectors.toList());
	}

	/**
	 * 计算两个物品的余弦相似度
	 * @param vector1 物品1的用户评分向量
	 * @param vector2 物品2的用户评分向量
	 * @return 相似度分数
	 */
	private double calculateSimilarity(Map<Integer, Double> vector1, Map<Integer, Double> vector2) {
		double norm1 = 0.0, norm2 = 0.0, dotProduct = 0.0;

		for (Map.Entry<Integer, Double> entry : vector1.entrySet()) {
			Integer key = entry.getKey();
			if (vector2.containsKey(key)) {
				dotProduct += entry.getValue() * vector2.get(key);
			}
			norm1 += Math.pow(entry.getValue(), 2);
		}

		for (Double value : vector2.values()) {
			norm2 += Math.pow(value, 2);
		}

		if (norm1 == 0.0 || norm2 == 0.0) return 0.0;
		return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
	}
}